<?php
/**
 * 定义和生成SOAP的WSDL
 * 
 * @version $Id: Wsdl.php 135 2011-04-20 03:30:14Z linsir123 $
 */
class Wsdl
{
	/**
	 * @var WSDL路径
	 */
	private $wsdlUrl;
	
	/**
	 * @var 需要映射的类名称
	 */
	private $className;
	
	/**
	 * @var 服务名称
	 */
	private $serviceName;
	
	///
	private $headerWSDL;
	private $portTypeWSDL;
	private $bindingWSDL;
	private $serviceWSDL;
	private $messageWSDL;
	
	/**
	 * 构造函数
	 */
	public function __construct($wsdlUrl, $className, $serviceName)
	{
		$this->wsdlUrl = $wsdlUrl;
		$this->className = $className;
		$this->serviceName = $serviceName;
	}
	
	/**
	 * 取得WSDL
	 */
	public function getWSDL()
	{
		if (empty($this->className)) 
			throw new Exception('No class name.');
			
		if (empty($this->serviceName)) 
			throw new Exception('No service name.');
			
		$reflection = new ReflectionClass($this->className);
		if (!$reflection->isInstantiable()) 
			throw new Exception('Class is not instantiable.');
			
		// 初始化
		$this->messageWSDL = "";
		$this->_initHeader();
		$this->_initService();
		$this->_initPortType();
		$this->_initBinding();
		
		// 通过设置的Class生构造出WSDL
		$methods = $reflection->getMethods();
		foreach ($methods as $method) 
			$this->_parseMethod($method);
			
		$this->portTypeWSDL .= "</portType>\n";
		$this->bindingWSDL .= "</binding>\n";
		return sprintf('%s%s%s%s%s%s', 
			$this->headerWSDL, $this->portTypeWSDL, $this->bindingWSDL, $this->serviceWSDL, $this->messageWSDL, '</definitions>');
	}
	
	/**
	 * 解析方法
	 */
	private function _parseMethod($method)
	{
		if(!$method->isPublic()) 
			return false;
			
		$methodName = $method->getName();
		$comment = $method->getDocComment();
		if (strpos($comment, '@soap') === false) 
			return false;
			
		///
		$this->portTypeWSDL .= "<operation name=\"{$methodName}\">\n<input message=\"tns:{$methodName}Request\" />\n<output message=\"tns:{$methodName}Response\" />\n</operation>\n";
		$this->bindingWSDL .= "<operation name=\"{$methodName}\">\n<soap:operation soapAction=\"urn:{$this->serviceName}#{$this->className}#{$methodName}\" />\n<input><soap:body use=\"encoded\" namespace=\"urn:{$this->serviceName}\" encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\" />\n</input>\n<output>\n<soap:body use=\"encoded\" namespace=\"urn:{$this->serviceName}\" encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\" />\n</output>\n</operation>\n";
	    $this->messageWSDL .= "<message name=\"{$methodName}Request\">\n";
		
	    ///
	    $comment = preg_replace('/^\s*\**(\s*?$|\s*)/m', '', $comment);
		if (preg_match('/^@return\s+([\w\.]+(\[\s*\])?)\s*?(.*)$/im', $comment, $matches)) 
			$return = trim($matches[1]);
		else
			$return = null;
			
		$params = $method->getParameters();
		preg_match_all('/^@param\s+([\w\.]+(\[\s*\])?)\s*?(.*)$/im', $comment, $matches);
	    foreach ($params as $key => $val) 
			$this->messageWSDL .= "<part name=\"{$val->getName()}\" type=\"xsd:{$matches[1][$key]}\" />\n";
			
		///
		$this->messageWSDL .= "</message>\n";
		$this->messageWSDL .= "<message name=\"{$methodName}Response\">\n";
		$this->messageWSDL .= "<part name=\"{$methodName}\" type=\"xsd:{$return}\" />\n";
		$this->messageWSDL .= "</message>\n";
	}
	
	/**
	 * WSDL头部
	 */
	private function _initHeader()
	{
		$this->headerWSDL = "<?xml version=\"1.0\" ?>\n";
		$this->headerWSDL .= "<definitions name=\"{$this->serviceName}\" targetNamespace=\"urn:{$this->serviceName}\" xmlns:wsdl=\"http://schemas.xmlsoap.org/wsdl/\" xmlns:soap=\"http://schemas.xmlsoap.org/wsdl/soap/\" xmlns:tns=\"urn:{$this->serviceName}\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:SOAP-ENC=\"http://schemas.xmlsoap.org/soap/encoding/\" xmlns=\"http://schemas.xmlsoap.org/wsdl/\">\n";
		$this->headerWSDL .= "<types xmlns=\"http://schemas.xmlsoap.org/wsdl/\" />\n";
	}
	
	/**
	 * Service
	 */
	private function _initService()
	{
		$this->serviceWSDL = "<service name=\"{$this->serviceName}\">\n<documentation />\n<port name=\"{$this->serviceName}Port\" binding=\"tns:{$this->serviceName}Binding\"><soap:address location=\"{$this->wsdlUrl}\" />\n</port>\n</service>\n";
	}
	
	/**
	 * PortType
	 */
	private function _initPortType()
	{
		$this->portTypeWSDL = "<portType name=\"{$this->serviceName}Port\">";
	}
	
	/**
	 * Binding
	 */
	private function _initBinding()
	{
		$this->bindingWSDL = "<binding name=\"{$this->serviceName}Binding\" type=\"tns:{$this->serviceName}Port\">\n<soap:binding style=\"rpc\" transport=\"http://schemas.xmlsoap.org/soap/http\" />\n";
	}
}
